#!/usr/bin/python

# To change this template, choose Tools | Templates
# and open the template in the editor.

#Structure of live data: dummy_ld={'name':'dummy','EUI':'dummy','year':float('nan'),'month':float('nan'),'day':float('nan'),'hour':float('nan'),'min':float('nan'),'sec':float('nan'),'watts':float('nan'),'cg_watts':float('nan'),'cc_watts':float('nan'),'freq':float('nan'),'voltage':float('nan'),'current':float('nan'),'ontime':float('nan'),'vars':float('nan'),'cg_vars':float('nan'),'cc_vars':float('nan'),'phase':float('nan'),'eq_ontime':float('nan')}

__author__="gabor"
__date__ ="$2010.10.14. 21:16:43$"


#####################
## IMPORTS
#####################

import dbus, dbus.glib
import threading
import gobject
import time
import calendar
import datetime
import math
import os
import copy

#####################
## GLOBALS
#####################
# Storing the actual live information from the target device
latest_ld={}
# Storing the EUI of the device to analize
target_EUI=''

# Trigger level for bulb (W)
level_bulb=3
#Trigger level for compressor (W)
level_compressor_init=45

# state variables of the fridge. 0 means 'off', 1 means 'on', -1 means undetermined
compressor_state=-1
bulb_state=-1

#variable to store previous actual power consumption
watts_prev=0;

# Time between RM Calls: ensures that bus is not overloaded
bus_sleep_time=2





#####################
## FUNCTIONS
#####################

# Reads EUI of the target Plogg from target.txt.
# Returns 0 if target.txt can'b be read
def get_target_EUI():
    try:
        f_target_handle=open('target.txt','r')
        EUI=f_target_handle.read(16)
        return EUI
    except IOError:
        return ''

# Check if a folder is exists, and create one, if not
# Returns 0 if the folder can't be created
def checkandmadedir(dirname):

    folder_exists=os.access(dirname+'/',os.F_OK)

    if (folder_exists==0):
        try:
            os.mkdir(dirname+'/')
        except OSError:
            return 0;
    return 1

# Converts Plogg's year, month, etc. values to Unix timestamp format
# Returns a float NaN value in case of invalid parameters, else returns unix timestamp
def ploggtime2stamp(y,mo,d,h,mi,s):
    # Converting device timestamp to Unix format
    # converting to int
    try:
        year_int=int(y)
        month_int=int(mo)
        day_int=int(d)
        hour_int=int(h)
        min_int=int(mi)
        sec_int=int(s)

        weekday=calendar.weekday(year_int,month_int,day_int)
        yearday=datetime.date(year_int,month_int,day_int).toordinal()-datetime.date(year_int,1,1).toordinal()+1
        devicetime=time.mktime((year_int,month_int,day_int,hour_int,min_int,sec_int,weekday,yearday,-1))

    except ValueError:
        devicetime=float('nan')
    return devicetime

def update_avp(EUI,interval,P):
    ## Opening and reading accumulated average
##    try:        #to open existing file
##        file=open(EUI+'/average_p.txt','r')
##        line=file.readlines()
##        values=line[0].split(',')
##        time_saved=float(values[0])
##        P_saved=float(values[1])
##        #print 'Read from file',time_saved,P_saved
##        file.close()
##        #time_saved,P_saved=readvalues[0]
##        #print time_saved,P_saved
##    except IOError:
##        print 'IOerror at reading'+EUI+'/average_p.txt'
##        time_saved=0
##        P_saved=0
##    except ValueError:
##        print 'Valueerror at reading'+EUI+'/average_p.txt'
##        time_saved=0
##        P_saved=0
    (time_saved,P_saved)=get_avp(EUI)

    #calculating accumulated average
    total_time=time_saved+interval;
    P_new=(time_saved*P_saved+interval*P)/total_time
    print 'Total average consumption: %f W in %d seconds'%(P_new,int(total_time))

    try:
        file=open(EUI+'/average_p.txt','w')
        file.write('%f,%f,\n'%(total_time,P_new))
        file.close()
    except IOError:
        print 'IOError at writing'+EUI+'/average_p.txt'
        print 'Can\'t write new average value'

    return

def get_avp(EUI):
    try:        #to open existing file
        file=open(EUI+'/average_p.txt','r')
        line=file.readlines()
        values=line[0].split(',')
        time_saved=float(values[0])
        P_saved=float(values[1])
        file.close()

    except IOError:
        print 'IOerror at reading '+EUI+'/average_p.txt'
        time_saved=0
        P_saved=0
    except ValueError:
        print 'Valueerror at reading'+EUI+'/average_p.txt'
        time_saved=0
        P_saved=0
    return (time_saved,P_saved)

def detect_state(EUI,latest_ld):
    global level_bulb
    global level_compressor_init
    global compressor_state
    global bulb_state
    global watts_prev
    #creating local copy from latest_ld
    latest_ld_local=copy.deepcopy(latest_ld)
    # getting accumulated average value
    (time_saved,P_saved)=get_avp(EUI)
    #Setting trigger levels
    #We haven't got average comsumption value yet (8 hours -> 28800
    if (time_saved<28800):
        level_compressor=level_compressor_init
    else:
        level_compressor=P_saved

    #detecting compressor state
    if latest_ld_local['watts']>level_compressor:
        compressor_state=1;
    else:
        compressor_state=0;

    #detecting bulb state
    if (compressor_state==0):
        if latest_ld_local['watts']>level_bulb:
            bulb_state=1;
        else:
            bulb_state=0;
    #Detecting while compressor is on
    else:
        p_jump=latest_ld_local['watts']-watts_prev
        if (p_jump>level_bulb)&(p_jump<level_compressor):   # positive jump with correct size
            bulb_state=1
        elif (p_jump<(-level_bulb))&(p_jump>(-level_compressor)):    #negative jump with correct size
            bulb_state=0
        else:
            pass #no change in bulb_state without jump
        
    #Shifting conuption data
    watts_prev=latest_ld_local['watts']
    #Saving state information to file
    save_state(EUI,latest_ld_local,compressor_state,bulb_state)

    return

def save_state(EUI,latest_ld,compressor_state,bulb_state):
    filename=EUI+'/state.txt'
    timestamp=ploggtime2stamp(latest_ld['year'],latest_ld['month'],latest_ld['day'],latest_ld['hour'],latest_ld['min'],latest_ld['sec'])
    try:
        f_log_handle=open(filename,'r')
        exists=1
        #print('%s file exists.'%filename)
        f_log_handle.close()
    except IOError:
        exists=0

    try:
        f_log_handle=open(filename,'a')
        # Printing header if file does not exist
        if (exists==0):
            f_log_handle.write("#Timestamp\t\tComp.\tBulk\r\n")

        f_log_handle.write('%f\t'%(timestamp))
        if math.isnan(timestamp):
            f_log_handle.write('%d\t%d\r\n'%(-1,-1))
        else:
            f_log_handle.write('%d\t%d\r\n'%(compressor_state,bulb_state))

    except IOError:
        print('excetion at file opening')
        return 0

    return


def main():
    print 'MIT_PM_FRIDGE_ANALIZER\n\n'
    print 'This application connects to the mit_pm_manager script through'
    print 'a dbus interface, named \'ca.amk.pm_service\','
    print 'and analizes data from Plogg specified in target.txt\n\n'

    global target_EUI

    #Getting target EUI from config file
    target_EUI=get_target_EUI()
    print 'Target EUI:',target_EUI
    if (target_EUI==''):
        print 'Can\'t read target.txt!'
        return
    #Creating folder if necessary
    checkandmadedir(target_EUI);
    #Starting thread of continuous data fetching
    #This thread calls the state-detector function
    thr_fetcher().start()

    #Starting thread which manages average P
    thr_average().start()
   



#####################
## THREADS
#####################

## Thread for fetching live data from mit_pm_manager
class thr_fetcher(threading.Thread):
    def run(self):
        global latest_ld
        global target_EUI
        global bus_sleep_time
        bus = dbus.SessionBus()
        changed=1
        while 1:
            try:
                Pm_service = dbus.Interface(bus.get_object('ca.amk.pm_service', '/'),'ca.amk.Interface')
                devices=Pm_service.get_device_EUIs(1)
                
                if target_EUI in devices:
                    temp_ld=Pm_service.get_live_data(target_EUI)
                    if (len(temp_ld)==18):
                        #Deciding if we have new value
                        if (len(latest_ld)==18):
                            timestamp_temp=ploggtime2stamp(temp_ld['year'],temp_ld['month'],temp_ld['day'],temp_ld['hour'],temp_ld['min'],temp_ld['sec'])
                            timestamp_latest=ploggtime2stamp(latest_ld['year'],latest_ld['month'],latest_ld['day'],latest_ld['hour'],latest_ld['min'],latest_ld['sec'])
                            if timestamp_latest==timestamp_temp:
                                changed=0       #New data is not fetched


                        if (changed):       ##
                            latest_ld=copy.deepcopy(temp_ld)
                            detect_state(target_EUI,latest_ld)
                        changed=1           ## Resetting changed variable
            except dbus.exceptions.DBusException,err:
                print str(err)
                print 'Manager service isn\'t running. Waiting...'
            time.sleep(bus_sleep_time)

# Thread for managing average consumption
class thr_average(threading.Thread):
    def run(self):
        global latest_ld
        global target_EUI
        # waiting for valid data and setting initial parameters
        while 1:
            # creating local copy of latest_ld
            latest_ld_local=copy.deepcopy(latest_ld)

            if len(latest_ld_local)==18:                  ## Something is fetched..
                if math.isnan(latest_ld_local['hour']):   ## But it is invalid data..
                    time.sleep(0.1)
                    continue                        ## Retries after some time
                else:                               ## Valid data
                    timestamp_prev=ploggtime2stamp(latest_ld_local['year'],latest_ld_local['month'],latest_ld_local['day'],latest_ld_local['hour'],latest_ld_local['min'],latest_ld_local['sec'])
                    cc_watts_prev=latest_ld_local['cc_watts']
                    break                           ## Exiting from this loop because we have the data that we want
            else:                                   ## Nothing is fetched..
                time.sleep(0.1)
                continue                            ## Retries after some time

        # Starting loop of calculating actual average consumption
        while 1:
            # creating local copy of latest_ld
            latest_ld_local=copy.deepcopy(latest_ld)

            timestamp_latest=ploggtime2stamp(latest_ld_local['year'],latest_ld_local['month'],latest_ld_local['day'],latest_ld_local['hour'],latest_ld_local['min'],latest_ld_local['sec'])
            if (timestamp_latest-timestamp_prev)>360:       # if at least 6 min. is elapsed from previous fetching
                time_interval=timestamp_latest-timestamp_prev
                consumed=latest_ld_local['cc_watts']-cc_watts_prev
                # Calculating average P
                # consumed*1000/(time_interval/3600)=consumed/time_interval*3,600,000
                P_average=(consumed*3600000)/time_interval
                print 'Average power consumpion in the last %f seconds: %f W'%(time_interval,P_average)
                ## Updating average.txt
                update_avp(target_EUI,time_interval,P_average)
                ## Refreshing prev_values
                timestamp_prev=timestamp_latest
                cc_watts_prev=latest_ld_local['cc_watts']
            #Waiting for some time before rechecking latest live information
            time.sleep(1)


       


#####################
## MAIN LOOP
#####################
if __name__ == "__main__":
    main()







